#!/usr/bin/env python3
# pur_ui.py
# 界面模块：负责显示界面
import sys
import time
import numpy as np
import math
import random
import subprocess
import platform
import os
import socket
import threading
import cv2
import yaml
from PyQt5.QtWidgets import (
    QApplication,
    QMainWindow,
    QWidget,
    QVBoxLayout,
    QHBoxLayout,
    QTableWidget,
    QTableWidgetItem,
    QLabel,
    QHeaderView,
    QSplitter,
    QPushButton,
    QStatusBar,
    QGroupBox,
    QGridLayout,
    QComboBox,
    QCheckBox,
)
from PyQt5.QtGui import QPixmap, QImage, QPainter, QPen, QColor, QBrush, QFont, QIcon
from PyQt5.QtCore import Qt, QTimer, QPointF, QRectF, QRect
from ultralytics import YOLO


class VideoServer:
    """视频服务器类：监听0.0.0.0:6003，接收多个客户端视频流，缓存最新帧"""

    def __init__(self):
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.server_socket.bind(("0.0.0.0", 6003))
        self.server_socket.listen(5)
        self.clients = {}  # {addr: (frame, timestamp)}
        self.running = True
        self.accept_thread = threading.Thread(
            target=self.accept_connections, daemon=True
        )
        self.accept_thread.start()
        print("VideoServer started on 0.0.0.0:6003")

    def accept_connections(self):
        while self.running:
            try:
                client_socket, addr = self.server_socket.accept()
                client_thread = threading.Thread(
                    target=self.handle_client, args=(client_socket, addr), daemon=True
                )
                client_thread.start()
            except Exception as e:
                if self.running:
                    print(f"Accept error: {e}")
                break

    def handle_client(self, client_socket, addr):
        print(f"New client connected: {addr}")
        while self.running:
            try:
                # 接收帧大小
                size_data = client_socket.recv(4)
                if len(size_data) != 4:
                    break
                size = int.from_bytes(size_data, "big")
                if size <= 0:
                    break
                # 接收数据
                data = b""
                while len(data) < size:
                    packet = client_socket.recv(min(4096, size - len(data)))
                    if not packet:
                        break
                    data += packet
                if len(data) == size:
                    # 解码帧
                    nparr = np.frombuffer(data, np.uint8)
                    frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
                    if frame is not None:
                        timestamp = time.time()
                        self.clients[addr] = (frame, timestamp)
            except Exception as e:
                print(f"Handle client {addr} error: {e}")
                break
        client_socket.close()
        if addr in self.clients:
            del self.clients[addr]
        print(f"Client disconnected: {addr}")

    def stop(self):
        self.running = False
        if hasattr(self, "server_socket"):
            self.server_socket.close()
        print("VideoServer stopped")


class VideoPanel(QWidget):
    """视频显示面板：下拉选择源，显示最新帧"""

    def __init__(self, video_server, panel_id, parent=None):
        super().__init__(parent)
        self.video_server = video_server
        self.panel_id = panel_id  # 用于标识面板，如1,2,3
        self.annotate_enabled = False
        self.model = YOLO(
            os.path.abspath(
                os.path.join(os.path.dirname(__file__), "../datas/yolov11.pt")
            )
        )
        self_layout = QVBoxLayout(self)
        self_layout.addWidget(QLabel(f"视频源 {panel_id}:"))
        # 下拉栏和复选框布局
        source_layout = QHBoxLayout()
        self.combo = QComboBox()
        self.combo.addItem("无")
        source_layout.addWidget(self.combo)
        self.annotate_checkbox = QCheckBox("标注")
        self.annotate_checkbox.stateChanged.connect(self.on_annotate_changed)
        source_layout.addWidget(self.annotate_checkbox)
        self_layout.addLayout(source_layout)
        self.video_label = QLabel()
        self.video_label.setMinimumSize(400, 200)  # 调整为适合三个面板的高度
        self.video_label.setAlignment(Qt.AlignCenter)
        self.video_label.setStyleSheet(
            "border: 1px solid gray; background-color: black;"
        )
        self_layout.addWidget(self.video_label, 1)  # 添加伸展因子，使视频区域扩展
        self.selected_addr = None
        self.combo.currentTextChanged.connect(self.on_selection_changed)
        # 定时更新
        self.update_timer = QTimer(self)
        self.update_timer.timeout.connect(self.update_combo_and_display)
        self.update_timer.start(100)

    def on_annotate_changed(self, state):
        self.annotate_enabled = state == Qt.Checked

    def set_source(self, addr):
        """自动设置源"""
        ip, port = addr
        text = f"{ip}:{port}"
        self.combo.setCurrentText(text)
        # 这会触发on_selection_changed

    def update_combo_and_display(self):
        current_text = self.combo.currentText()
        # 更新下拉选项
        self.combo.clear()
        self.combo.addItem("无")
        for addr in sorted(self.video_server.clients.keys()):
            ip, port = addr
            self.combo.addItem(f"{ip}:{port}")
        # 恢复当前选择
        if current_text != "无" and current_text in [
            self.combo.itemText(i) for i in range(1, self.combo.count())
        ]:
            self.combo.setCurrentText(current_text)
        # 更新显示
        self.update_display()

    def on_selection_changed(self, text):
        if text == "无":
            self.selected_addr = None
            self.video_label.clear()
        else:
            parts = text.split(":")
            if len(parts) == 2:
                self.selected_addr = (parts[0], int(parts[1]))
        self.update_display()

    def update_display(self):
        if self.selected_addr and self.selected_addr in self.video_server.clients:
            frame, ts = self.video_server.clients[self.selected_addr]
            if frame is not None:
                # 如果启用标注，进行YOLO推理
                if self.annotate_enabled:
                    results = self.model.predict(frame, verbose=False)
                    annotated_frame = results[0].plot(labels=False)
                    frame = annotated_frame
                # cv2 BGR to RGB
                frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                height, width, channel = frame_rgb.shape
                bytes_per_line = 3 * width
                q_image = QImage(
                    frame_rgb.data, width, height, bytes_per_line, QImage.Format_RGB888
                )
                pixmap = QPixmap.fromImage(q_image)
                # 缩放到标签大小
                pixmap = pixmap.scaled(
                    self.video_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation
                )
                # 添加时间戳到左下角
                painter = QPainter(pixmap)
                painter.setPen(QColor(255, 255, 255))
                painter.setFont(QFont("Arial", 10))
                time_str = time.strftime("%H:%M:%S", time.localtime(ts))
                painter.drawText(10, pixmap.height() - 10, f"Updated: {time_str}")
                painter.end()
                self.video_label.setPixmap(pixmap)
        else:
            self.video_label.clear()


class GraphMapWidget(QWidget):
    def __init__(self, graph_manager=None, parent=None):
        super().__init__(parent)
        self.graph = graph_manager
        self.robots = []
        self.belief_pr = None
        self.belief_pos = None
        self.scale_factor = 1.0
        self.offset_x = 0
        self.offset_y = 0
        self.capture_count = 0
        # 地图相关
        self.map_pixmap = None
        self.map_width = 0
        self.map_height = 0
        self.resolution = 0.05  # 默认分辨率
        self.origin = [0.0, 0.0]
        self.map_offset_x = 0
        self.map_offset_y = 0
        self.map_scale_factor = 1.0
        # 定义角色颜色
        self.role_colors = {
            "evader": QColor(255, 0, 0),  # 红色 - 逃避者
            "pursuer": QColor(0, 0, 255),  # 蓝色 - 追捕者
        }
        self.setMinimumSize(800, 600)
        self.setMouseTracking(True)
        # 加载地图
        self.load_map()
        if self.map_pixmap is None:
            if self.graph is not None:
                self.update_graph_view()
        else:
            self.update_map_view()

    def load_map(self):
        """加载地图yaml和pgm文件"""
        yaml_path = os.path.abspath(
            os.path.join(os.path.dirname(__file__), "../datas/outdoor_final.yaml")
        )
        if os.path.exists(yaml_path):
            try:
                with open(yaml_path, "r") as f:
                    map_data = yaml.safe_load(f)
                image_rel = map_data["image"]
                image_path = os.path.abspath(
                    os.path.join(os.path.dirname(yaml_path), image_rel)
                )
                self.map_image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
                if self.map_image is not None:
                    self.map_height, self.map_width = self.map_image.shape[:2]
                    self.resolution = map_data["resolution"]
                    self.origin = map_data["origin"][:2]  # 只取x,y，忽略z
                    h, w = self.map_height, self.map_width
                    qimg = QImage(
                        self.map_image.data, w, h, w, QImage.Format_Grayscale8
                    )
                    self.map_pixmap = QPixmap.fromImage(qimg)
                    print(
                        f"地图加载成功: {image_path}, 分辨率: {self.resolution}, 原点: {self.origin}"
                    )
                else:
                    print(f"无法读取地图图像: {image_path}")
            except Exception as e:
                print(f"加载地图失败: {e}")
        else:
            print(f"地图YAML文件不存在: {yaml_path}")

    def update_map_view(self):
        """更新地图视图变换"""
        if self.map_pixmap is None:
            return
        map_w = self.map_pixmap.width()
        map_h = self.map_pixmap.height()
        scale_x = self.width() / map_w
        scale_y = self.height() / map_h
        self.map_scale_factor = min(scale_x, scale_y)
        scaled_w = map_w * self.map_scale_factor
        scaled_h = map_h * self.map_scale_factor
        self.map_offset_x = (self.width() - scaled_w) / 2
        self.map_offset_y = (self.height() - scaled_h) / 2

    def update_graph_view(self):
        """更新图视图变换（无地图时）"""
        if (
            not hasattr(self.graph, "node_coordinates")
            or not self.graph.node_coordinates
        ):
            return
        # 找到节点的边界
        min_x = min(x for x, y in self.graph.node_coordinates)
        max_x = max(x for x, y in self.graph.node_coordinates)
        min_y = min(y for x, y in self.graph.node_coordinates)
        max_y = max(y for x, y in self.graph.node_coordinates)
        # 计算缩放因子
        width_ratio = (self.width() - 100) / (max_x - min_x) if max_x - min_x > 0 else 1
        height_ratio = (
            (self.height() - 100) / (max_y - min_y) if max_y - min_y > 0 else 1
        )
        self.scale_factor = min(width_ratio, height_ratio)
        # 计算偏移量以使图形居中
        self.offset_x = (
            self.width() - (max_x - min_x) * self.scale_factor
        ) / 2 - min_x * self.scale_factor
        self.offset_y = (
            self.height() - (max_y - min_y) * self.scale_factor
        ) / 2 - min_y * self.scale_factor

    def world_to_pixel(self, x, y):
        """将世界坐标转换为像素坐标"""
        if self.map_pixmap is not None:
            # 世界坐标到地图像素（考虑origin和resolution）
            map_px = (x - self.origin[0]) / self.resolution
            map_py = self.map_height - (
                (y - self.origin[1]) / self.resolution
            )  # Y轴翻转
            # 地图像素到widget像素（使用地图缩放和偏移）
            wx = self.map_offset_x + map_px * self.map_scale_factor
            wy = self.map_offset_y + map_py * self.map_scale_factor
            return int(wx), int(wy)
        else:
            # 无地图时的旧逻辑（基于节点坐标的缩放）
            if (
                not hasattr(self.graph, "node_coordinates")
                or not self.graph.node_coordinates
            ):
                return int(x), int(y)
            # 计算缩放和平移后的位置
            scaled_x = x * self.scale_factor + self.offset_x
            scaled_y = y * self.scale_factor + self.offset_y
            # 反转Y轴：将数学坐标系转换为屏幕坐标系
            flipped_y = self.height() - scaled_y
            return int(scaled_x), int(flipped_y)

    def paintEvent(self, event):
        """绘制地图和机器人"""
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        # 绘制底图
        if self.map_pixmap is not None:
            self.update_map_view()  # 更新变换
            map_w = self.map_pixmap.width()
            map_h = self.map_pixmap.height()
            scaled_w = map_w * self.map_scale_factor
            scaled_h = map_h * self.map_scale_factor
            # 修正：使用 QRect 目标矩形，实现整个 pixmap 的自动缩放到居中位置
            target_rect = QRect(
                int(self.map_offset_x),
                int(self.map_offset_y),
                int(scaled_w),
                int(scaled_h),
            )
            painter.drawPixmap(
                target_rect, self.map_pixmap
            )  # 自动缩放整个源到 target_rect
        else:
            # 无地图时填充背景
            painter.fillRect(self.rect(), QColor(240, 240, 240))
            if (
                not hasattr(self.graph, "node_coordinates")
                or not self.graph.node_coordinates
            ):
                painter.setPen(QPen(Qt.black))
                painter.setFont(QFont("Arial", 12))
                painter.drawText(self.rect(), Qt.AlignCenter, "没有地图数据可用")
                return
        # 绘制图和机器人（无论是否有地图）
        node_num = (
            len(self.graph.node_coordinates)
            if hasattr(self.graph, "node_coordinates")
            else 0
        )
        if node_num > 0:
            # 绘制底图：细线边 + 空心圆圈节点
            node_radius = 4
            edge_width = 1
            for i in range(node_num):
                # 绘制节点：空心圆圈
                x, y = self.graph.node_coordinates[i]
                px, py = self.world_to_pixel(x, y)
                painter.setPen(QPen(QColor(80, 80, 80), 1))
                painter.setBrush(QBrush(Qt.NoBrush))  # 空心
                painter.drawEllipse(
                    px - node_radius, py - node_radius, node_radius * 2, node_radius * 2
                )
                # 绘制边（细线）
                for j in range(i + 1, node_num):
                    if (
                        hasattr(self.graph, "adj_matrix")
                        and i < len(self.graph.adj_matrix)
                        and j < len(self.graph.adj_matrix[i])
                    ):
                        if self.graph.adj_matrix[i][j] == 1:
                            jx, jy = self.graph.node_coordinates[j]
                            pjx, pjy = self.world_to_pixel(jx, jy)
                            painter.setPen(QPen(QColor(180, 180, 180), edge_width))
                            painter.drawLine(px, py, pjx, pjy)
            # 绘制信念状态（追捕者视角下的逃逸者可能位置）
            if (
                self.belief_pr is not None
                and self.belief_pos is not None
                and len(self.belief_pr) == node_num
            ):
                max_pr = np.max(self.belief_pr)
                belief_base_radius = 4
                belief_scale = 8  # 最大半径 = base + scale
                for i in range(node_num):
                    if self.belief_pos[i]:
                        x, y = self.graph.node_coordinates[i]
                        px, py = self.world_to_pixel(x, y)
                        pr = self.belief_pr[i]
                        gray_intensity = int(200 - pr * 200)
                        belief_color = QColor(gray_intensity, 255, gray_intensity)
                        radius = belief_base_radius + int(pr * belief_scale)
                        painter.setPen(QPen(belief_color.darker(), 1))
                        painter.setBrush(QBrush(belief_color))
                        painter.drawEllipse(
                            px - radius, py - radius, radius * 2, radius * 2
                        )
                        # 绘制置信度标签（可选，小字体）
                        if pr >= 0.1:  # 只显示显著置信
                            painter.setPen(QPen(Qt.black))
                            painter.setFont(QFont("Arial", 6))
                            painter.drawText(px + radius + 2, py + 2, f"{pr:.1f}")
            # 绘制真实机器人位置（监控视角）
            for robot in self.robots:
                if (
                    "position" not in robot
                    or robot["position"] is None
                    or "node" not in robot
                    or robot["node"] is None
                ):
                    continue
                try:
                    # 使用节点坐标绘制（更精确）
                    node_id = int(robot["node"])
                    if 0 <= node_id < node_num:
                        x, y = self.graph.node_coordinates[node_id]
                    else:
                        # 回退到解析字符串
                        pos_str = robot["position"].strip("()")
                        x, y = map(float, pos_str.split(","))
                    px, py = self.world_to_pixel(x, y)
                except:
                    continue
                # 根据角色选择颜色和样式
                role = robot.get("role", "unknown")
                color = self.role_colors.get(role, QColor(128, 128, 128))
                robot_radius = 10
                if role == "evader":
                    # 逃逸者：红色圆圈（空心）
                    painter.setPen(QPen(color, 2))
                    painter.setBrush(QBrush(Qt.NoBrush))
                else:  # pursuer
                    # 追捕者：蓝色实心圆
                    painter.setPen(QPen(color.darker(), 2))
                    painter.setBrush(QBrush(color.lighter(150)))
                painter.drawEllipse(
                    px - robot_radius,
                    py - robot_radius,
                    robot_radius * 2,
                    robot_radius * 2,
                )
                # 绘制机器人ID和角色
                painter.setPen(QPen(Qt.black))
                painter.setFont(QFont("Arial", 9, QFont.Bold))
                role_symbol = (
                    "E" if role == "evader" else "P" if role == "pursuer" else "?"
                )
                painter.drawText(px - 15, py - 15, f"{robot['id']}{role_symbol}")
                # 绘制目标位置（如果有）
                if (
                    "target" in robot
                    and robot["target"] != "未设置"
                    and "target_node" in robot
                    and robot["target_node"] != "未设置"
                ):
                    try:
                        target_node_id = int(robot["target_node"])
                        if 0 <= target_node_id < node_num:
                            tx, ty = self.graph.node_coordinates[target_node_id]
                        else:
                            target_str = robot["target"].strip("()")
                            tx, ty = map(float, target_str.split(","))
                        tpx, tpy = self.world_to_pixel(tx, ty)
                        # 绘制目标线和点
                        painter.setPen(QPen(color, 2, Qt.DashLine))
                        painter.drawLine(px, py, tpx, tpy)
                        painter.setPen(QPen(color.darker(), 2))
                        painter.setBrush(QBrush(Qt.NoBrush))
                        target_point_radius = 5
                        painter.drawEllipse(
                            tpx - target_point_radius,
                            tpy - target_point_radius,
                            target_point_radius * 2,
                            target_point_radius * 2,
                        )
                    except:
                        pass
                # 绘制状态指示器（外圈）
                status = robot.get("status", "unknown")
                if status == "active":
                    status_color = Qt.green
                elif status == "connecting":
                    status_color = Qt.yellow
                elif status == "disconnected":
                    status_color = Qt.red
                else:
                    status_color = Qt.gray
                painter.setPen(QPen(status_color, 2))
                painter.drawEllipse(
                    px - robot_radius - 3,
                    py - robot_radius - 3,
                    (robot_radius + 3) * 2,
                    (robot_radius + 3) * 2,
                )
            # 绘制标题和捕获次数
            painter.setPen(QPen(Qt.black))
            painter.setFont(QFont("Arial", 14, QFont.Bold))
            painter.drawText(20, 30, "追捕游戏 - 实时监控")
            painter.setFont(QFont("Arial", 12))
            painter.drawText(20, 60, f"捕获次数: {self.capture_count}")

    def resizeEvent(self, event):
        """窗口大小改变时重新计算缩放因子"""
        super().resizeEvent(event)
        if self.map_pixmap is not None:
            self.update_map_view()
        else:
            self.update_graph_view()

    def update_robots(self, robots, capture_count=0, belief_pr=None, belief_pos=None):
        """更新机器人状态和信念"""
        self.robots = robots
        self.belief_pr = belief_pr
        self.belief_pos = belief_pos
        self.capture_count = capture_count
        self.update()


class RobotInfoTable(QTableWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        # 修改列数和表头
        self.setColumnCount(6)
        self.setHorizontalHeaderLabels(
            ["Robot", "当前位置", "初始位置", "目标位置", "状态", "更新"]
        )

        # 设置表格样式
        self.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
        self.verticalHeader().setVisible(False)
        self.setSelectionBehavior(QTableWidget.SelectRows)
        self.setEditTriggers(QTableWidget.NoEditTriggers)

        # 设置列宽
        self.setColumnWidth(0, 80)  # Robot
        self.setColumnWidth(1, 120)  # 当前位置
        self.setColumnWidth(2, 120)  # 初始位置
        self.setColumnWidth(3, 120)  # 目标位置
        self.setColumnWidth(4, 80)  # 状态
        self.setColumnWidth(5, 80)  # 更新

        self.setStyleSheet(
            """
            QTableWidget {
                font-size: 10px;
                gridline-color: #e0e0e0;
            }
            QHeaderView::section {
                background-color: #f0f0f0;
                padding: 4px;
                border: 1px solid #d0d0d0;
                font-weight: bold;
            }
        """
        )

    def update_robots(self, robots):
        """更新机器人信息表格"""
        self.setRowCount(len(robots))
        for row, robot in enumerate(robots):
            # Robot: ID (role)
            robot_str = f"{robot['id']} ({robot['role']})"
            name_item = QTableWidgetItem(robot_str)
            name_item.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter)
            self.setItem(row, 0, name_item)

            # 当前位置
            pos_item = QTableWidgetItem(robot.get("position", "未知"))
            self.setItem(row, 1, pos_item)

            # 初始位置
            initial_item = QTableWidgetItem(robot.get("initial_position", "未记录"))
            self.setItem(row, 2, initial_item)

            # 目标位置
            target_item = QTableWidgetItem(robot.get("target", "未设置"))
            self.setItem(row, 3, target_item)

            # 状态
            status = robot.get("status", "未知")
            status_item = QTableWidgetItem(status)
            status_item.setTextAlignment(Qt.AlignCenter)
            # 根据状态设置颜色
            if status == "active":
                status_item.setForeground(QColor(0, 128, 0))  # 绿色
            else:
                status_item.setForeground(QColor(200, 0, 0))  # 红色
            self.setItem(row, 4, status_item)

            # 最后更新时间
            last_update_secs = robot.get("last_update", 0)
            update_item = QTableWidgetItem(f"{last_update_secs:.1f}s")
            update_item.setTextAlignment(Qt.AlignCenter)
            # 根据更新时间设置颜色
            if last_update_secs > 10:
                update_item.setForeground(QColor(200, 0, 0))  # 红色
            elif last_update_secs > 5:
                update_item.setForeground(QColor(200, 100, 0))  # 橙色
            self.setItem(row, 5, update_item)

            # 设置行背景色 (可选，根据状态区分)
            if status == "disconnected":
                for col in range(6):
                    item = self.item(row, col)
                    if item:
                        item.setBackground(QColor(245, 245, 245))  # 浅灰色


class PursuitGameUI(QMainWindow):
    def __init__(self, server=None, logger=None):
        super().__init__()
        self.server = server
        self.setWindowTitle("不完全信息追捕游戏监控系统")
        self.setGeometry(100, 100, 1200, 800)
        self.logger = logger
        # 启动视频服务器
        self.video_server = VideoServer()
        # 创建三个视频面板
        self.video_panels = [
            VideoPanel(self.video_server, 1),
            VideoPanel(self.video_server, 2),
            VideoPanel(self.video_server, 3),
        ]
        # 跟踪之前的clients以检测新接入
        self.prev_clients = set()
        # 创建主布局
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)
        # 主分割器（水平）
        main_splitter = QSplitter(Qt.Horizontal)
        # 左侧：地图和机器人表格
        left_widget = QWidget()
        left_layout = QVBoxLayout(left_widget)
        # 地图组件
        graph = server.graph if server and hasattr(server, "graph") else None
        self.map_widget = GraphMapWidget(graph)
        left_layout.addWidget(self.map_widget, 1)  # 地图占剩余空间
        # 添加伸展项以推表格到下部
        left_layout.addStretch()
        # 机器人表格（固定高度，对齐左下）
        self.robot_table = RobotInfoTable()
        self.robot_table.setFixedHeight(150)  # 固定高度，不随窗口变化
        left_layout.addWidget(self.robot_table)
        # 右侧：视频面板
        right_widget = QWidget()
        right_layout = QVBoxLayout(right_widget)
        for panel in self.video_panels:
            right_layout.addWidget(panel)
        # 添加到主分割器
        main_splitter.addWidget(left_widget)
        main_splitter.addWidget(right_widget)
        main_splitter.setSizes([600, 600])  # 左右均衡
        main_layout.addWidget(main_splitter)
        # 控制按钮和状态栏布局（确保一行）
        control_status_layout = QHBoxLayout()
        control_status_layout.setContentsMargins(0, 5, 0, 5)  # 紧凑边距
        # 按钮
        self.pause_btn = QPushButton("暂停游戏")
        self.pause_btn.clicked.connect(self.toggle_pause)
        control_status_layout.addWidget(self.pause_btn)
        self.reset_btn = QPushButton("位置重置")
        self.reset_btn.clicked.connect(self.position_reset)
        control_status_layout.addWidget(self.reset_btn)
        # 状态标签（设置固定宽度以确保一行）
        self.game_status_label = QLabel("游戏状态: 等待连接")
        self.game_status_label.setFixedWidth(120)
        control_status_layout.addWidget(self.game_status_label)
        self.capture_label = QLabel("捕获次数: 0")
        self.capture_label.setFixedWidth(100)
        control_status_layout.addWidget(self.capture_label)
        self.belief_label = QLabel("信念: 支持位置 0 | 熵 0.00")
        self.belief_label.setFixedWidth(180)
        control_status_layout.addWidget(self.belief_label)
        self.online_label = QLabel("在线: 0/3")
        self.online_label.setFixedWidth(80)
        control_status_layout.addWidget(self.online_label)
        self.last_update_label = QLabel(f"更新: {time.strftime('%H:%M:%S')}")
        self.last_update_label.setFixedWidth(120)
        control_status_layout.addWidget(self.last_update_label)
        control_status_layout.addStretch()  # 推到右边
        # 创建容器widget并设置固定高度
        control_widget = QWidget()
        control_widget.setLayout(control_status_layout)
        control_widget.setFixedHeight(40)  # 固定高度，不随窗口变化
        main_layout.addWidget(control_widget)
        # 底部状态栏（只占一行）
        self.status_bar = self.statusBar()
        self.status_bar.setFixedHeight(25)  # 固定高度确保一行
        self.time_label = QLabel(time.strftime("%H:%M:%S"))
        self.status_bar.addPermanentWidget(self.time_label)
        # 更新定时器
        self.update_timer = QTimer(self)
        self.update_timer.timeout.connect(self.update_display)
        self.update_timer.start(200)

    def closeEvent(self, event):
        """窗口关闭时停止视频服务器"""
        self.video_server.stop()
        super().closeEvent(event)

    def toggle_pause(self):
        """切换暂停/恢复游戏"""
        if self.server:
            self.server.toggle_game_active()

    def position_reset(self):
        """触发位置重置"""
        if self.server:
            self.server.position_reset()
            if self.logger:
                self.logger.info("位置重置已触发")

    def update_display(self):
        """更新UI显示"""
        try:
            # 从服务器获取机器人信息和游戏状态
            robots = []
            game_active = False
            capture_count = 0
            belief_pr = None
            belief_pos = None

            if self.server:
                # 检查并从服务器获取所有机器人信息
                if hasattr(self.server, "get_all_robots_info"):
                    robots = self.server.get_all_robots_info()
                # 获取游戏状态
                if hasattr(self.server, "get_game_active"):
                    game_active = self.server.get_game_active()
                if hasattr(self.server, "get_capture_count"):
                    capture_count = self.server.get_capture_count()
                # 获取信念数据
                if hasattr(self.server, "get_belief_pr") and hasattr(
                    self.server, "get_belief_pos"
                ):
                    belief_pr = self.server.get_belief_pr()
                    belief_pos = self.server.get_belief_pos()
            # 更新暂停按钮文本
            self.pause_btn.setText("恢复游戏" if game_active else "暂停游戏")

            # 更新地图（传入信念）
            self.map_widget.update_robots(robots, capture_count, belief_pr, belief_pos)
            # 更新表格
            self.robot_table.update_robots(robots)
            # 更新状态标签
            # self.game_status_label.setText(f"游戏状态: {'<font color=\'green\'>进行中</font>' if game_active else '<font color=\'orange\'>暂停</font>'}")
            game_status_text = (
                "<font color='green'>进行中</font>"
                if game_active
                else "<font color='orange'>暂停</font>"
            )
            self.game_status_label.setText(f"游戏状态: {game_status_text}")
            self.capture_label.setText(f"捕获次数: {capture_count}")
            # 计算信念摘要
            if belief_pos is not None and belief_pr is not None:
                support_count = np.sum(belief_pos)
                if support_count > 0:
                    entropy = -np.sum(
                        belief_pr[belief_pos] * np.log2(belief_pr[belief_pos] + 1e-10)
                    )
                else:
                    entropy = 0.0
                self.belief_label.setText(
                    f"信念: 支持位置 {support_count} | 熵 {entropy:.2f}"
                )
            else:
                self.belief_label.setText("信念: 未初始化")
            # 在线计数
            online_count = sum(1 for r in robots if r.get("status") == "active")
            self.online_label.setText(f"在线: {online_count}/3")
            self.last_update_label.setText(f"更新: {time.strftime('%H:%M:%S')}")
            # 状态栏时间
            self.time_label.setText(time.strftime("%H:%M:%S"))

            # 自动分配新接入的视频源
            current_clients = set(self.video_server.clients.keys())
            new_addrs = current_clients - self.prev_clients
            self.prev_clients = current_clients
            for new_addr in sorted(new_addrs):  # 排序以稳定
                for panel in self.video_panels:
                    if panel.selected_addr is None:
                        panel.set_source(new_addr)
                        break
        except Exception as e:
            if self.logger:
                self.logger.error(f"更新显示错误: {e}")
            else:
                print(f"更新显示错误: {e}")
